package optimizers.commons;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import fitness.FitnessResult;

import util.MyUtil;

import model.Enzyme;
import model.OligoSystem;
import model.OligoSystemGeneral;

public class GAIndiv implements Cloneable{
	
	public double[] genes;
	
	public HashMap<String,Object> annotations;// might be changed to a more specific type

	public RunConfig config;
	
	public Map<Integer,String> inttoname = new HashMap<Integer,String>();
	public Map<String,Integer> nametoint = new HashMap<String,Integer>();

	public FitnessResult fitnessResult = null;
	
	//TODO: this should be the basic representation for individuals
	
	//Genes represent the values of the different parameters of the system.
	//The bounds and standard values of those parameters are defined in the RunConfig.
	//Parameters should only be present if evolved.
	//Annotations contains additional informations required by the various GAs that are not
	//common to all of them. They can also carry informations to make the implementations of
	//evolution more efficient, such as which sequence is present or not (so that evolving
	//the corresponding seqK or seqC is irrelevant).
	//
	//Genes are made of the parameters given in the following order:
	// SeqK stability parameter of SIMPLE sequences
	// InhK stability parameter of inhibitors when inhibiting a template
	// SeqC initial concentration of sequences
	// Templates template concentrations
	// exo parameters
	// pol parameters
	// nick parameters
	//
	// The size of the different sections depends on nbSimpleSequences.
	// Note: the template part should be mostly zeros. Individuals not following this
	// recommendation, while valid, will take a very large amount of time to simulate
	public GAIndiv(double[] genes, int nbSimpleSequences, RunConfig config, HashMap<String,Object> annotations){
		this.genes = genes;
		this.config = config;
		this.annotations = annotations;
		if(annotations == null){
			this.annotations = new HashMap<String,Object>();
		}
		if(this.annotations.get("nbSimpleSequences") == null || !this.annotations.get("nbSimpleSequences").equals(nbSimpleSequences)){
			this.annotations.put("nbSimpleSequences", nbSimpleSequences);
		}
		for(int i = 0; i< nbSimpleSequences; i++){
			inttoname.put(i, "s"+i);
			nametoint.put("s"+i, i);
		}
		for(int i = 1; i < nbSimpleSequences +1; i++){
			for(int j = 0; j < nbSimpleSequences; j++){
				inttoname.put(i*nbSimpleSequences+j, "I"+i+""+j);
				nametoint.put("I"+i+""+j, i*nbSimpleSequences+j);
			}
		}
	}

	//TODO: should have a function more generic than that.
	//TODO: deprecated?
	public GAIndiv addTwoSimpleSequences(){
		return null;
	}
	
	public GAIndiv clone(){
		try {
			return (GAIndiv) super.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	
	public OligoSystemGeneral getOligoSystem(){
		int nbSimpleSequences = (Integer) this.annotations.get("nbSimpleSequences");
//		if(this.nametoint.size() != nbSimpleSequences){
//			System.err.println("probably incorrect");
//		}
		double[][] SeqK = new double[nbSimpleSequences+1][nbSimpleSequences];
		double[][] InhK = new double[nbSimpleSequences+1][nbSimpleSequences];
		double[][][] templates = new double[nbSimpleSequences+1][nbSimpleSequences][nbSimpleSequences];
		double[][] SeqC = new double[nbSimpleSequences+1][nbSimpleSequences];
		int index = 0; // registers where in the genome we are, based on what is evolved, and what isn't.
		
		//SeqK simple sequences
		
		for(int i = 0; i< nbSimpleSequences; i++){
			if(this.config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES){
				SeqK[0][i] = this.genes[index];
				if(SeqK[0][i]==0.0){
					System.out.println("Aaaarg");
				}
				index++;
			} else {
				SeqK[0][i] = this.config.seqK.defaultValue;
			}
		}
		
		//SeqK inhibiting sequences
		
		for(int i=0; i<nbSimpleSequences; i++){
			for(int j=0; j<nbSimpleSequences; j++){
				if(this.config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES){
					SeqK[i+1][j] = this.genes[index]/model.Constants.ratioInhibition;
					InhK[i+1][j] = this.genes[index];
					index++;
				} else if (this.config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_NO_7_6_RULE){
					if(SeqK[0][i] != 0 && SeqK[0][j] != 0){
						InhK[i+1][j] =  MyUtil.calculateInhK(
								SeqK[0][i], SeqK[0][j]);
						
					} else {
						InhK[i+1][j] = 0;
					}
					SeqK[i+1][j] = InhK[i+1][j] / model.Constants.ratioInhibition;
				} else {
					InhK[i+1][j] = this.config.inhK.defaultValue;
					SeqK[i+1][j] = InhK[i+1][j] / model.Constants.ratioInhibition;
				}
			}
		}
		
		//SeqC
		
		for(int i = 0; i < nbSimpleSequences + 1; i++){
			for (int j = 0; j < nbSimpleSequences; j++){
				if(this.config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES){
					SeqC[i][j] = this.genes[index];
					index++;
				} else {
					SeqC[i][j] = this.config.seqC.defaultValue;
				}
			}
		}
		
		//Templates
		//Since this also denotes the STRUCTURE of the individual, this part is always present.
		
		for(int i = 0; i< nbSimpleSequences + 1; i++){
			for(int j = 0; j < nbSimpleSequences; j++){
				for(int k = 0; k < nbSimpleSequences; k++){
					if(this.config.template.optimizeMechanism == RunConfig.TEMPLATE_OPTIMIZE_YES){
						templates[i][j][k] = Math.max(this.genes[index],0);
						index++;
					} else {
						
							templates[i][j][k] = this.genes[index] == 0 ? 0 : this.config.template.defaultValue;
						index++;
					}
				}
			}
		}
		
		//Exo
		Enzyme exo = new Enzyme("exo",this.config.exoActivity.defaultValue,this.config.exoSimpleKm==null?model.Constants.exoKmSimple:this.config.exoSimpleKm.defaultValue,this.config.exoInhibKm==null?model.Constants.exoKmInhib:this.config.exoInhibKm.defaultValue);
		
		//if we evolve the activity
		if(this.config.exoActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			exo.activity = this.genes[index];
			index++;
		}
		//if we evolve the Km (affinity) of simple sequences for the exonuclease
		if(this.config.exoSimpleKm != null && this.config.exoSimpleKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			exo.basicKm = this.genes[index];
			index++;
		}
		//if we evolve the Km (affinity) of inhibiting sequences for the exonuclease
		if(this.config.exoInhibKm != null && this.config.exoInhibKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			exo.optionalValue = this.genes[index];
			index++;
		}
		
		//Polymerase
		Enzyme poly = new Enzyme("poly",this.config.polActivity.defaultValue,this.config.polKm==null?model.Constants.polKm:this.config.polKm.defaultValue,this.config.polDoubleStrandKm==null?model.Constants.polKmBoth:this.config.polDoubleStrandKm.defaultValue);
		
		//if we evolve the activity
		if(this.config.polActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			poly.activity = this.genes[index];
			index++;
		}
		//if we evolve the Km (affinity) of simple sequences for the polymerase
		if(this.config.polKm != null && this.config.polKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			poly.basicKm = this.genes[index];
			index++;
		}
		//if we evolve the Km (affinity) of fully double-stranded templates for the polymerase
		if(this.config.polDoubleStrandKm != null && this.config.polDoubleStrandKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			poly.optionalValue = this.genes[index];
			index++;
		}
		
		//Nicking enzyme
		Enzyme nick = new Enzyme("nick",this.config.nickActivity.defaultValue,this.config.nickKm==null?model.Constants.nickKm:this.config.nickKm.defaultValue);
		
		//if we evolve the activity
		if(this.config.nickActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			nick.activity = this.genes[index];
			index++;
		}
		//if we evolve the Km (affinity) of simple sequences for the polymerase
		if(this.config.nickKm != null && this.config.nickKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			nick.basicKm = this.genes[index];
			index++;
		}
		
		OligoSystemGeneral oligo = new OligoSystemGeneral(nbSimpleSequences, templates, SeqK, InhK, SeqC, exo, poly, nick, inttoname, nametoint );
		return oligo;
	}
	
	public double getFitness() {
		if (this.fitnessResult != null) {
			return this.fitnessResult.finalResult();
		} else {
			return 0;
		}
	}
	
	public void mutate(){
		
	}
	
}
